package org.etnaframework.module.localcache;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;

/**
 * 全量内存缓存 构造器
 *
 * <pre>
 * 功能和特性：
 *
 * 1、数据全部保存在本地内存，定时全量更新 + 检查点更新，有极高的读效率
 *    只有更新时才会去请求后端数据，业务只使用本地内存中的数据，所以也不存在缓存击穿的问题
 *    如果请求后端数据失败（例如数据库挂了），本地还可以继续使用上次的缓存数据提供服务，能尽量减少对线上业务的影响
 *    该缓存适用于【读多写少】的场景，如果有高频的写入/变更需求，建议还是用中心化的缓存组件
 *
 * 2、全量更新任务触发时间对齐
 *    本缓存组件做了刷新时间对齐机制，即同一个缓存对象，在不同的服务器上全量刷新的触发时机是一致的（服务器需先做好校时）
 *    例如更新周期为5min，那么所有的机器上，都是 每5min的第43s开始执行
 *    （秒数由类名+方法名字符串hash得来，只要名称相同则秒数是一样的，也支持用户自定义cron表达式）
 *    可以在在多实例部署时，尽量减少在不同实例上缓存内容不一致的情况
 *
 * 3、检查点更新，使用redis的订阅/发布机制，可以在后端数据有变化时，更快地同步到所有的缓存实例
 *    支持全量更新和增量更新，注意增量更新不能保证绝对可靠，需要有定时全量更新机制保底，内部实现机制参见{@link LocalCacheCore}
 *
 * 缓存实例类型和适用场景：
 *
 * {@link LocalCacheType1}
 *     Type1 定时全量重新加载，没有其他更新机制
 *
 * {@link LocalCacheType2}
 *     Type2 定时全量重新加载 + 检查点全量更新
 *
 * {@link LocalCacheType3}
 *     Type3 定时全量重新加载 + 检查点全量/增量更新
 * </pre>
 *
 * @author jasyaf
 * @since 2023-11-01
 */
public final class LocalCache {

    public static final Logger log = LoggerFactory.getLogger(LocalCache.class);

    /**
     * 创建并初始化Type1类型的缓存（定时全量重新加载，没有其他更新机制）
     *
     * @param name                     缓存的名称，要求全局唯一不允许重复
     * @param fullUpdateIntervalMinute 定时全量加载时间间隔，单位分钟，允许的值为 1/2/3/4/5/6/10/12/15/20/30/60，如果需要更灵活的配置请使用cron表达式
     * @param timeoutSec               加载任务执行超时时间，单位秒，如果超时会触发{@link ILocalCacheCustomizer#onTimeout(String, long)}，一次加载任务只会输出一次超时告警
     * @param customizer               需要使用者实现的方法
     */
    public static <T> LocalCacheType1<T> newType1(String name, int fullUpdateIntervalMinute, long timeoutSec, ILocalCacheCustomizer<T> customizer) {
        if (fullUpdateIntervalMinute < 1 || fullUpdateIntervalMinute > 60 || 60 % fullUpdateIntervalMinute != 0) {
            throw new IllegalArgumentException(LocalCacheType1.class.getSimpleName() + "(name=" + name + ") failed to configure fullUpdateIntervalMinute, value range 1/2/3/4/5/6/10/12/15/20/30/60");
        }
        // 生成定时任务的cron，为了尽量避免任务集中执行造成系统扛不住，根据name计算出一个固定的时间偏移量，实现错峰执行
        int offsetSec = Math.abs(name.hashCode()) % 60;
        String scheduledFullUpdateCron = offsetSec + " */" + fullUpdateIntervalMinute + " * * * *";
        return new LocalCacheType1<>(name, scheduledFullUpdateCron, timeoutSec, customizer);
    }

    /**
     * 创建并初始化Type1类型的缓存（定时全量重新加载，没有其他更新机制）
     *
     * @param name                    缓存的名称，要求全局唯一不允许重复
     * @param scheduledFullUpdateCron 定时全量加载触发时间表达式，使用cron格式（最小单位秒），例如想配置每小时的第10分钟第15秒执行 15 10 * * * *
     * @param timeoutSec              加载任务执行超时时间，单位秒，如果超时会触发{@link ILocalCacheCustomizer#onTimeout(String, long)}，一次加载任务只会输出一次超时告警
     * @param customizer              需要使用者实现的方法
     */
    public static <T> LocalCacheType1<T> newType1(String name, String scheduledFullUpdateCron, long timeoutSec, ILocalCacheCustomizer<T> customizer) {
        return new LocalCacheType1<>(name, scheduledFullUpdateCron, timeoutSec, customizer);
    }

    /**
     * 创建并初始化Type2类型的缓存（定时全量重新加载 + 检查点全量更新）
     *
     * @param name                        缓存的名称，要求全局唯一不允许重复
     * @param fullUpdateIntervalMinute    定时全量加载时间间隔，单位分钟，允许的值为 1/2/3/4/5/6/10/12/15/20/30/60，如果需要更灵活的配置请使用cron表达式
     * @param timeoutSec                  加载任务执行超时时间，单位秒，如果超时会触发{@link ILocalCacheCustomizer#onTimeout(String, long)}，一次加载任务只会输出一次超时告警
     * @param redisConnectionFactory      redis连接工厂
     * @param checkpointUpdateIntervalSec 检查点更新任务执行周期，单位秒，允许的值为 1/2/3/4/5/6/10/12/15/20/30
     * @param customizer                  需要使用者实现的方法
     */
    public static <T> LocalCacheType2<T> newType2(String name, int fullUpdateIntervalMinute, long timeoutSec, RedisConnectionFactory redisConnectionFactory, int checkpointUpdateIntervalSec, ILocalCacheCustomizer<T> customizer) {
        if (fullUpdateIntervalMinute < 1 || fullUpdateIntervalMinute > 60 || 60 % fullUpdateIntervalMinute != 0) {
            throw new IllegalArgumentException(LocalCacheType2.class.getSimpleName() + "(name=" + name + ") failed to configure the full load time interval, range 1/2/3/4/5/6/10/12/15/20/30/60");
        }
        // 生成定时任务的cron，为了尽量避免任务集中执行造成系统扛不住，根据name计算出一个固定的时间偏移量，实现错峰执行
        int offsetSec = Math.abs(name.hashCode()) % 60;
        String scheduledFullUpdateCron = offsetSec + " */" + fullUpdateIntervalMinute + " * * * *";
        return new LocalCacheType2<>(name, scheduledFullUpdateCron, timeoutSec, redisConnectionFactory, checkpointUpdateIntervalSec, customizer);
    }

    /**
     * 创建并初始化Type2类型的缓存（定时全量重新加载 + 检查点全量更新）
     *
     * @param name                        缓存的名称，要求全局唯一不允许重复
     * @param scheduledFullUpdateCron     定时全量加载触发时间表达式，使用cron格式（最小单位秒），例如想配置每小时的第10分钟第15秒执行 15 10 * * * *
     * @param timeoutSec                  加载任务执行超时时间，单位秒，如果超时会触发{@link ILocalCacheCustomizer#onTimeout(String, long)}，一次加载任务只会输出一次超时告警
     * @param redisConnectionFactory      redis连接工厂
     * @param checkpointUpdateIntervalSec 检查点更新任务执行周期，单位秒，允许的值为 1/2/3/4/5/6/10/12/15/20/30
     * @param customizer                  需要使用者实现的方法
     */
    public static <T> LocalCacheType2<T> newType2(String name, String scheduledFullUpdateCron, long timeoutSec, RedisConnectionFactory redisConnectionFactory, int checkpointUpdateIntervalSec, ILocalCacheCustomizer<T> customizer) {
        return new LocalCacheType2<>(name, scheduledFullUpdateCron, timeoutSec, redisConnectionFactory, checkpointUpdateIntervalSec, customizer);
    }

    /**
     * 创建并初始化Type3类型的缓存（定时全量重新加载 + 检查点全量/增量更新）
     *
     * @param name                        缓存的名称，要求全局唯一不允许重复
     * @param fullUpdateIntervalMinute    定时全量加载时间间隔，单位分钟，允许的值为 1/2/3/4/5/6/10/12/15/20/30/60，如果需要更灵活的配置请使用cron表达式
     * @param timeoutSec                  加载任务执行超时时间，单位秒，如果超时会触发{@link ILocalCacheCustomizer#onTimeout(String, long)}，一次加载任务只会输出一次超时告警
     * @param redisConnectionFactory      redis连接工厂
     * @param checkpointUpdateIntervalSec 检查点更新任务执行周期，单位秒，允许的值为 1/2/3/4/5/6/10/12/15/20/30
     * @param customizer                  需要使用者实现的方法
     */
    public static <T> LocalCacheType3<T> newType3(String name, int fullUpdateIntervalMinute, long timeoutSec, RedisConnectionFactory redisConnectionFactory, int checkpointUpdateIntervalSec, ILocalCacheCustomizerType3<T> customizer) {
        if (fullUpdateIntervalMinute < 1 || fullUpdateIntervalMinute > 60 || 60 % fullUpdateIntervalMinute != 0) {
            throw new IllegalArgumentException(LocalCacheType3.class.getSimpleName() + "(name=" + name + ") failed to configure the full load time interval, range 1/2/3/4/5/6/10/12/15/20/30/60");
        }
        // 生成定时任务的cron，为了尽量避免任务集中执行造成系统扛不住，根据name计算出一个固定的时间偏移量，实现错峰执行
        int offsetSec = Math.abs(name.hashCode()) % 60;
        String scheduledFullUpdateCron = offsetSec + " */" + fullUpdateIntervalMinute + " * * * *";
        return new LocalCacheType3<>(name, scheduledFullUpdateCron, timeoutSec, redisConnectionFactory, checkpointUpdateIntervalSec, customizer);
    }

    /**
     * 创建并初始化Type3类型的缓存（定时全量重新加载 + 检查点全量/增量更新）
     *
     * @param name                        缓存的名称，要求全局唯一不允许重复
     * @param scheduledFullUpdateCron     定时全量加载触发时间表达式，使用cron格式（最小单位秒），例如想配置每小时的第10分钟第15秒执行 15 10 * * * *
     * @param timeoutSec                  加载任务执行超时时间，单位秒，如果超时会触发{@link ILocalCacheCustomizer#onTimeout(String, long)}，一次加载任务只会输出一次超时告警
     * @param redisConnectionFactory      redis连接工厂
     * @param checkpointUpdateIntervalSec 检查点更新任务执行周期，单位秒，允许的值为 1/2/3/4/5/6/10/12/15/20/30
     * @param customizer                  需要使用者实现的方法
     */
    public static <T> LocalCacheType3<T> newType3(String name, String scheduledFullUpdateCron, long timeoutSec, RedisConnectionFactory redisConnectionFactory, int checkpointUpdateIntervalSec, ILocalCacheCustomizerType3<T> customizer) {
        return new LocalCacheType3<>(name, scheduledFullUpdateCron, timeoutSec, redisConnectionFactory, checkpointUpdateIntervalSec, customizer);
    }
}
